Utilisation de ListView dans Android
Afficher des listes de vues.
Principe d'une ListView
ListView est une vue à intégrer comme n'importe quelle autre vue dans un layout.
Elle est très pratique pour afficher une série d'éléments identiques (ou avec des variantes) dans un scroll.
Elle permet aussi d'optimiser la mémoire en "recyclant" les vues de chaque "cellule".
A noter qu'une grande partie de ce qui s'applique aux ListView s'applique aussi aux GridView.
Utiliser une ListView : un Adapter et un Listener
Pour configurer et utiliser une ListView, on utilise la plupart du temps 2 interfaces :
ListAdapter et OnItemClickListener
L'indispensable ListAdapter
Le principe est qu'une ListView à besoin d'un objet qui va l'informer de ce qu'elle doit afficher.
Cet objet doit implémenter l'interface ListAdapter. C'est cet objet qui va par exemple décider du nombre d'éléments à afficher dans la liste, et à créer les vues pour chaque cellules.
A noter que cet adapter peut être utilisé par d'autres classes, en particulier par GridView.
Pour définir un adapter pour notre liste, il suffit d'avoir un objet qui implémente l'interface ListAdapter, et de le passer à notre liste :
maSuperListe.setAdapter(monSuperAdapter);
Les méthodes de l'interface
L'interface défini 12 méthodes ! On peut les regrouper en 3 parties :
- Les méthodes qui concernent la liste complète.
- Les méthodes qui concernent un élément de la liste en particulier.
- Les méthodes qui permettent de savoir qui utilise notre adapter.
Concernant la liste complète
int getCount()
Cette méthode doit renvoyer le nombre d'élément total contenu par la liste.
boolean isEmpty()
Cette méthode doit indiquer si la liste est complètement vide ou pas.
int getViewTypeCount()
Cette méthode doit indiquer le nombre d'élements de type différent dans la liste.
Les éléments sont considérés de types différents s'ils ne vont pas utilliser la même vue. Si tous les éléments de la liste auront le même type, il suffit d'écrire return 1
boolean areAllItemsEnabled()
Cette méthode détermine si tous les éléments sont clickables, ou si certains ne le sont pas.
Si vous répondez "false", alors la méthode isEnabled() sera appellée pour chaque éléments.
boolean hasStableIds()
Inutile pour nous, on repondra systématiquement true.
Concernant un élément de la liste
Ces méthodes seront appellées automatiquement pour chacun de nos éléments.
Elles recevront alors en paramètre int position, qui corespond à la position dans la liste. .
View getView(int position, View convertView, ViewGroup parent)
Cette méthode doit renvoyer la vue pour l'élément. Ici on peut soit construire une vue "à la main", soit utiliser un layout XML.
Voir le chapitre plus loin pour cette méthode.
boolean isEnabled(int position)
Permet de déterminer si l'élément est actif ou non. Elle ne sera appellée que si areAllItemsEnabled a répondu false.
int getItemViewType(int position)
Cette méthode doit renvoyer le type de l'élément. Le type est simplement un int, et est utilisé par Android pour le recyclage des vues. Voir le chapitre plus loin pour le recyclage de vues.
Si on a qu'un seul type d'élements (comme défini par getViewTypeCount), il suffit de renvoyer toujours la même valeur. Par exemple return 1.
Object getItem(int position)
Cette méthode doit renvoyer l'objet correspondant à l'élément. Que ce soit un String, ou un objet User, ou quoi que ce soit, il faut le revoyer ici.
Dans certains cas seulement cette méthode est utilisée, par exemple pour gérer les selections dans la liste.
long getItemId(int position)
Cette méthode permet de définir un identifiant unique l'élément. La plupart du temps, il suffit de donner la position de l'élément en guise d'identifiant.
Cet identifiant sera passé au listener au moment du click sur l'élément.
Concernant les utilisateurs de notre adapter
void registerDataSetObserver(DataSetObserver observer)
La plupart du temps, c'est la ListView qui interroge l'adapter pour savoir comment se comporter.
Il est parfois utile que l'adapter prévienne la ListView que quelque chose à changé dans les données.
C'ests la ListView s'inscrit auprès de l'adapter : l'observer reçu ici en parametre correspond à la ListView, mais "cachée derriere" une interface : DataSetObserver.
On pourra alors choisir prévenir cet observateur en cas de changement dans nos données en appellant des méthodes de DataSetObserver (onChanged(), par exemple).
void unregisterDataSetObserver(DataSetObserver observer)
La liste se désinscrit et ne souhaite plus recevoir de notification.
En résumé
Une fois la liste et l'adapter connectés (grace à la méthode list.setAdapter()) voici le déroulement typique :
Etape | méthode appellée | Exemple de valeur renvoyée par l'adapteur |
---|---|---|
Est ce que tous les élements de la liste sont actifs ? | areAllItemsEnabled() | true |
Combien y-a-t'il d'éléments dans la liste ? | getCount() | 15 |
Enregistre la liste en tant qu'observateur en cas de changement dans les données | registerDataObserver(instanceDeMaListe) | void |
Combien de types de vues différents ? | getViewTypeCount() | 1 |
... | ||
Quelle vue pour le premier élément ? | getView(0, null, maListe) | une View |
Quel est le type du premier élément ? | getItemViewType(0) | 1 |
Quelle vue pour le second élément ? | getView(1, null, maListe) | une View |
Quel est le type du second élément ? | getItemViewType(1) | 1 |
... | ||
Quelle vue pour le 15e élément ? | getView(14, laVueDeLElement1, maListe) | laVueDeLElement1, modifiée |
Quel est le type du second élément ? | getItemViewType(14) | 1 |
Dans le détail : getView()
Cette méthode est appellée pour chaque élément de la liste.
View getView(int position, View convertView, ViewGroup parent)
L'implémention la plus simple serait de construire une nouvelle vue à chaque fois :
TextView maCellule = new TextView(...); maCellule.setText("Cellule numéro " + position); return maCellule;
C'est tout à fait possible, mais pas recommandé. On préfère en général utilise le system service "LayoutInflater" pour lire un layout XML.
Attention, pour utiliser le LayoutInflater, on a besoin d'un Context...
LayoutInflater inflater = (LayoutInflater) monContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View maCellule = inflater.inflate(R.layout.list_cell, parent, false); return maCellule;
Recyclage
Enfin, il est possible de recycler certaines vues. C'est Android qui décide de nous permettre de recycler une vue.
C'est le rôle du parametre convertView. Il peut être NULL, ce qui signifie que l'on doit construire une nouvelle vue. S'il n'est pas NULL, on peut modifier cette vue pour l'élement demandé.
Voici une implémentation complete :public View getView(int position, View convertView, ViewGroup parent) { View cellView = convertView; if (cellView == null) { LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); cellView = inflater.inflate(R.layout.list_cell, parent, false); } TextView titleView = (TextView) cellView.findViewById(R.id.title); TextView contentView = (TextView) cellView.findViewById(R.id.content); titleView.setText("Ma cellule"); contentView.setText("position : " + position); return cellView; }
Le OnItemClickListener
Ce listener s'utilise très simplement pour savoir quel élément à été cliqué dans la liste. C'est le même principe que un onClickListener sur un boutton, mais la liste nous passe un peu plus d'informations.
Pour s'inscrire :
maSuperListe.setOnItemClickListener(monSuperListener);
L'interface OnItemClickListener nous oblige à implémenter la méthode :
public void onItemClick (AdapterView parent, View view, int position, long id)
Description des paramètres :
- parent : la liste qui a été cliquée. Pas la cellule, mais la liste. Utile si on a plusieurs listes avec le même listener.
- view : la vue de la cellule qui a été cliquée.
- position : le numéro de position de l'élément qui a été cliqué
- id : l'identificant de l'élément qui a été cliqué
TP : Liste de villes
Vous allez créer un simple projet qui affichera la liste des plus grandes ville du monde dans un ListView.
Les "données" seront simplement stockées dans un ArrayList, et l'adapter manipulera ce tableau pour fournir les informations à la ListView.
Projet
- Créez un nouveau projet dans Eclipse "ListViewDemo". Importez le avec Git dans BitBucket et invitez les enseignants.
- Le projet est composé d'une seule activity.
- Modifier le layout de l'activity pour y intégrer une ListView.
L'adapter
Initialisation
- Créez une classe MonAdapter, qui implémente l'interface ListAdapter.
- La classe MonAdapter doit avoir un constructeur qui prends en parametre un Context.
- Le constructeur doit assigner le context dans un membre de sa classe.
- Le constructeur doit aussi initialiser une List de chaine de caratère avec les 10 villes les plus peuplée au monde (Wikipedia est votre ami).
Implémenataion
- Implémentez toutes les méthodes de l'interface ListAdapter en vous servant de l'ArrayList.
- Ajoutez des logs pour chacunes des méthodes pour voir le déroulement des appels de méthodes.
- Créez un layout XML pour représenter une cellule (avec un TextView, par exemple).
- Les vues sont construitent en utilisant le LayoutInflater (voir implémentation plus haut).
Aller plus loin
- Pour chacune des villes, comment faire pour afficher le nombre d'habitants, en plus du nom de la ville ?
- Pour chacune des villes, comment faire pour afficher le drapeau du pays correspondant ?